package block

import (
	"blockchaingo/utils"
	"bytes"
	"crypto/sha256"
	"fmt"
	"math"
	"math/big"
)

var (
	maxNonce = math.MaxInt64
)

const targetBits = 20

type ProofOfWork struct {
	Block  *Block
	Target *big.Int
}

// 新的工作量证明，并且得到一个难度值
func NewProofOfWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-targetBits))

	pow := &ProofOfWork{Block: b, Target: target}

	return pow
}

//将区块体里面的数据转换成一个字节码数组，为下一个区块准备数据
func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join([][]byte{
		pow.Block.PrevBlockHash,
		pow.Block.HashTransactions(),
		utils.IntToHex(pow.Block.Timestamp),
		utils.IntToHex(int64(targetBits)),
		utils.IntToHex(int64(nonce)),
	}, []byte{})
	return data
}

// 运行工作量证明，开始挖矿，找到小于难度目标值的Hash
func (pow *ProofOfWork) Run() (int, []byte) {
	var hashInt big.Int
	var hash [32]byte

	nonce := 0

	fmt.Printf("Mining the block %x \n", pow.Block.HashTransactions())

	for nonce < maxNonce {
		data := pow.prepareData(nonce)

		hash = sha256.Sum256(data)

		//fmt.Printf("\r Dig %s into mine %x", nonce, hash)

		hashInt.SetBytes(hash[:])

		if hashInt.Cmp(pow.Target) == -1 {
			break
		} else {
			nonce++
		}
	}
	fmt.Printf("\r Dig into mine %d-%x", nonce, hash)
	fmt.Print("\n\n")
	return nonce, hash[:]
}

//验证工作量证明
func (pow *ProofOfWork) Validate() bool {
	var hashInt big.Int

	data := pow.prepareData(pow.Block.Nonce)

	hash := sha256.Sum256(data)
	hashInt.SetBytes(hash[:])

	isValid := hashInt.Cmp(pow.Target) == -1

	return isValid
}
